package events

import (
	"ewdetect/config"
	"ewdetect/locate"
	"ewdetect/stations"
	"math"
	"strings"
	"time"

	"github.com/rs/zerolog/log"
)

var Events []*Event

type Detection struct {
	PWaveArrival time.Time
	SWaveArrival time.Time
}

type Event struct {
	FirstDetectionLat      float64 //rad
	FirstDetectionLon      float64 //rad
	FirstPWaveDetection    time.Time
	Detections             map[string]Detection
	LastPWaveArrival       time.Time
	FinalConclusionReached bool
}

func IsEventOver(time time.Time, index int) bool {
	return time.Sub(Events[index].LastPWaveArrival) > config.EventTimeout
}

func FinalConclusionCheck(index int) {
	if IsEventOver(time.Now(), index) {
		if !Events[index].FinalConclusionReached {
			Events[index].FinalConclusionReached = true
			log.Info().Msgf("Final conclusion reached for event %d", index)
			Predict(index, true)
		}
	}
}

func Predict(index int, finalPrediction bool) {
	log.Info().Int("index", index).Bool("isFinalPrediction", finalPrediction).Msg("Predicting")
	formattedObservationList := []locate.Observation{}
	for detection := range Events[index].Detections {
		currentDetection := Events[index].Detections[detection]
		splitString := strings.Split(detection, "@")
		connection := splitString[0]
		stationID := splitString[1]
		formattedObservationList = append(formattedObservationList, locate.Observation{
			StationName:  detection,
			Lat:          (*stations.Stations[connection])[stationID].Lat * math.Pi / 180,
			Lon:          (*stations.Stations[connection])[stationID].Lon * math.Pi / 180,
			PWaveArrival: Events[index].FirstPWaveDetection.Sub(currentDetection.PWaveArrival).Seconds(),
			SWaveArrival: Events[index].FirstPWaveDetection.Sub(currentDetection.SWaveArrival).Seconds(),
		})
	}
	if len(formattedObservationList) >= config.NumberOfDetectionsThreshold {
		log.Debug().Interface("observations", formattedObservationList).Msg("Formatted observation list")
		eventName := GetEventName(index)
		finalGuess := locate.NelderMeadOptimization(eventName, &formattedObservationList, finalPrediction)
		dateOfEarthquake := Events[index].FirstPWaveDetection.Add(time.Duration(int64(finalGuess.Epoch) * 1e9))

		guessType := "Partial Earthquake Guess"
		if finalPrediction {
			guessType = "Final Earthquake Guess"
		}

		log.Info().
			Str("type", guessType).
			Int("detections", len(Events[index].Detections)).
			Str("date", dateOfEarthquake.Format("2006-01-02T15:04:05.000")).
			Float64("latitude", finalGuess.Lat*180/math.Pi).
			Float64("longitude", finalGuess.Lon*180/math.Pi).
			Float64("depth_km", finalGuess.Depth).
			Msg("Earthquake prediction")
	}
}

func GetEventName(index int) string {
	return Events[index].FirstPWaveDetection.Format("2006-01-02T15:04:05.000")
}

func NewDetection(connection, stationID string, pWaveArrival, sWaveArrival time.Time) (string, bool) {
	stationLat := (*stations.Stations[connection])[stationID].Lat * math.Pi / 180
	stationLon := (*stations.Stations[connection])[stationID].Lon * math.Pi / 180
	groupableEvent := false
	var index int
	for i, event := range Events {
		if locate.GreatCircleAngle(event.FirstDetectionLat, event.FirstDetectionLon, stationLat, stationLon) < config.EventExtent && !IsEventOver(pWaveArrival, i) {
			groupableEvent = true
			index = i
			log.Debug().Int("event_index", i).Msg("Found groupable event")
			break
		}
	}
	if !groupableEvent {
		Events = append(Events, &Event{
			FirstDetectionLat:      stationLat,
			FirstDetectionLon:      stationLon,
			FirstPWaveDetection:    pWaveArrival,
			Detections:             make(map[string]Detection),
			LastPWaveArrival:       pWaveArrival,
			FinalConclusionReached: false,
		})
		index = len(Events) - 1
		log.Info().
			Int("event_index", index).
			Float64("lat", stationLat*180/math.Pi).
			Float64("lon", stationLon*180/math.Pi).
			Msg("Created new event")
	}

	_, ok := Events[index].Detections[connection+"@"+stationID]
	if !ok {
		Events[index].LastPWaveArrival = pWaveArrival
		go FinalConclusionCheck(index)
	}
	Events[index].Detections[connection+"@"+stationID] = Detection{
		PWaveArrival: pWaveArrival,
		SWaveArrival: sWaveArrival,
	}

	Predict(index, false)
	return GetEventName(index), !ok
}
